'use strict';

const Service = require('egg').Service;
const {chromium, firefox} = require('playwright');
const got = require('../utils/got');
const cheerio = require('cheerio');

class SearchSvc extends Service {
  constructor(app) {
    super(app);
    // 搜索本地文章的分页大小
    this.limit = 10;
    // 搜索外部http的超时时间
    this.SEARCH_TIMEOUT = 4000;
    // 搜索结果缓存1小时
    this.CACHE_TIME = 3600;
  }


  /**
   * 捞取百度搜索结果
   */
  async fetchBaidu(kw, now_page) {
    const cache_key = `baidu2:${kw}:${now_page}`;
    let cache_value = await this.service.cacheSvc.get(cache_key);
    if (cache_value) {
      return cache_value;
    }

    let topics = await this.service.searchEngineSvc.searchBaidu(kw, now_page);
    // 因为搜索本身需要时间, 在这段时间内可能其他异步任务已经搜索完成set到redis中了, 所以还需要再判断一次
    cache_value = await this.service.cacheSvc.get(cache_key);
    if (cache_value) {
      return cache_value;
    }

    // if (topics.length > 0) {
    await this.service.cacheSvc.setex(cache_key, topics, this.CACHE_TIME);
    // }

    return topics;
  }


  /**
   * 捞取谷歌搜索结果
   */
  async fetchGoogle(kw, now_page) {
    const cache_key = `google2:${kw}:${now_page}`;
    let cache_value = await this.service.cacheSvc.get(cache_key);
    if (cache_value) {
      return cache_value;
    }

    let topics = await this.service.searchEngineSvc.searchGoogle(kw, now_page);
    // 因为搜索本身需要时间, 在这段时间内可能其他异步任务已经搜索完成set到redis中了, 所以还需要再判断一次
    cache_value = await this.service.cacheSvc.get(cache_key);
    if (cache_value) {
      return cache_value;
    }

    if (topics.length > 0) {
      await this.service.cacheSvc.setex(cache_key, topics, this.CACHE_TIME);
    }

    return topics;
  }


  /**
   * 捞取知乎搜索结果
   */
  async fetchZhihu(kw) {
    const cache_key = `zhihu2:${kw}`;
    const cache_value = await this.service.cacheSvc.get(cache_key);
    if (cache_value) {
      return cache_value;
    }

    let topics = await this.service.searchEngineSvc.searchZhihu(kw);
    if (topics.length > 0) {
      await this.service.cacheSvc.setex(cache_key, topics, this.CACHE_TIME);
    }

    return topics;
  }


  /*
   * 搜索站内文章
   */
  async searchTopic(kw, now_page, tab) {
    let result = [];

    try {
      const keywords = kw.trim().split(/\s+/).map(keyword => new RegExp(keyword, 'i'));
      const keywordConditions = keywords.map(keyword => ({title: {$regex: keyword}}));

      let query;
      let hint;
      if (tab) {
        query = {$and: [...keywordConditions, {is_public: true, tab: tab}]};
      } else {
        query = {$and: [...keywordConditions, {is_public: true}]};
      }

      const page_size = 10;

      const start = new Date();
      result = await this.ctx.model.Topic
        .find(query)
        .sort({last_reply_at: -1})
        .skip((now_page - 1) * page_size).limit(page_size)
        .lean();
      const end = new Date();
      this.ctx.logger.info(`搜索MongoDB: ${end - start}ms`);
    } catch (e) {
      this.ctx.logger.error(e);
    } finally {
      return result;
    }


  }


  /*
   * 根据关键字查询本地数据库
   */
  async searchLocal(query, keyword) {
    let {tab, sche} = query;
    const current_page = parseInt(query.page) || 1;
    let users = [];
    let topics = [];
    let data = [];
    let count = this.limit;
    let segmentKeywords = null;
    let page_size = this.limit;

    // 目的是不加&tab=topic 也能直接搜索内容
    tab = 'topic';

    switch (tab) {
      case 'user':
        [data, count] = await this.searchUser(keyword, current_page, page_size);
        break;
      case 'topic':
        // 如果配置项需要登录且用户已登录, 或者配置项中不需要登录的情况, 可以搜索百度
        if ((this.config.search_need_login && this.ctx.user) || !this.config.search_need_login) {
          this.service.cacheSvc.publish(`${keyword}:::::${current_page}`);
          this.ctx.curl(`${this.ctx.app.config.search_google_host}/crawlerTaosou?q=${encodeURIComponent(keyword)}&sche=${sche}&current_page=${current_page}`, {method: 'POST'});
        }
        [data, count, segmentKeywords] = await this.searchTopic(keyword, current_page);
        break;
      default:
        [users, topics] = await this.searchUserAndTopic(keyword, current_page);
    }


    let pages;
    if (tab == 'user') {
      pages = Math.ceil(count / page_size);
      if (pages > 10) {
        pages = 10;
      }
    } else {
      if ((this.config.search_need_login && this.ctx.user) || !this.config.search_need_login) {
        // 搜索默认只搜索10页内容, google也是如此
        pages = 10;
      } else {
        pages = Math.ceil(count / page_size);
        if (pages > 10) {
          pages = 10;
        }
      }
    }

    const base_url = `/search?q=${encodeURIComponent(keyword)}&page=`;


    return {
      keyword,
      data,
      users,
      topics,
      current_page: parseInt(query.page) || 1,
      tab,
      pages,
      count,
      segmentKeywords,
      base_url,
      showFooter: false,
      base: '/search?q=' + keyword,
    };
  }


  queryFactory(keyword, searchKey, pageIndex) {
    // 处理特殊字符, 避免正则表达式搜索时报错
    keyword = keyword.replace(/\+/g, '\\+')
      .replace(/\|/g, '\\|')
      .replace(/\$/g, '\\$')
      .replace(/\*/g, '\\*')
      .replace(/\?/g, '\\?')
      .replace(/\./g, '\\.')
      .replace(/\[/g, '\\[')
      .replace(/\]/g, '\\]')
      .replace(/\(/g, '\\(')
      .replace(/\)/g, '\\)')
      .replace(/\{/g, '\\{')
      .replace(/\}/g, '\\}');

    if (keyword.endsWith('\\')) {
      keyword = keyword.slice(0, keyword.length - 1);
    }

    // 支持多个词关联的模糊查询
    // 因为查询速度慢, 暂时取消支持
    // let keywordArray = this.ctx.helper.permuteArr(keyword);
    let keywordArray = [keyword.replace(/\s+/g, '.*')];

    const opt = {skip: (pageIndex - 1) * this.limit, limit: this.limit, sort: '-updatedAt'};
    return [
      {[searchKey]: keywordArray},
      opt,
    ];
  }


  /*
   * 根据关键字查找用户列表
   * @param {String} keyword 关键字, {Number} page 第几页
   * @return {Promise[data, count]} 承载用户列表, 查询总数的 Promise 对象
   */
  async searchUser(keyword, current_page, page_size) {
    const opt = {skip: (current_page - 1) * page_size, limit: page_size, sort: {updatedAt: -1, topic_count: -1}};

    let searchQuery;
    if (!keyword) {
      searchQuery = [{}, opt];
    } else {
      searchQuery = [{nickname: {'$regex': new RegExp(keyword, 'i')}}, opt];
    }


    let [users, count] = await Promise.all([
      this.service.userSvc.getUsersByQuery(...searchQuery),
      this.service.userSvc.getCountByQuery(...searchQuery),
    ]);

    for (let search_user of users) {
      if (this.ctx.user) {
        if (search_user.fans.map(v => v.name).includes(this.ctx.user.loginname)) {
          search_user.is_attention = true;
        } else {
          search_user.is_attention = false;
        }
      } else {
        search_user.is_attention = false;
      }
    }

    return [users, count];
  }


  /**
   * 根据传入的content获取title和真实url
   * @param content
   * @returns {finalTitle, finalUrl}
   */
  async findTitleAndUrl(content) {
    let url = content.match(/https?:\/\/[^\s]+/g);
    if (url) {
      url = url[0];
    }

    let finalTitle = url;
    let finalUrl = url;

    if (finalUrl.includes('.zhihu.com')) {
      const response = await got(finalUrl);
      const $ = cheerio.load(response.data);
      finalTitle = $('title').text();
      return this._fillFinalTile(finalTitle, finalUrl);
    }

    const browser = await chromium.launch({headless: true});
    try {
      let page = await browser.newPage();
      await page.goto(url, {waitUntil: 'domcontentloaded', timeout: 10000});
      await page.waitForSelector('body', {timeout: 10000, state: 'attached'});
      await page.waitForTimeout(1000);
      let title = await page.title();

      if (!title || title == '微信公众平台') {
        await page.waitForTimeout(2000);
        title = await page.title();
      }

      if (title && title != '验证码中间页' && title != '【抖音】记录美好生活-Douyin.com') {
        finalTitle = title;
      } else {
        let tmp = content.replace(/\n/g, '').match(/.+(?=https?:\/\/)/g);
        if (tmp) {
          finalTitle = tmp[0].replace(/.+\/ (.+)/, '$1').replace(/(^%)|( %)/g, ' #').trim();
        } else {
          finalTitle = content;
        }
      }
      finalUrl = page.url();

      const p = /[?&]previous_page=app_code_link&?/;
      finalUrl = finalUrl.replace(p, function (x) {
        if (x.startsWith('?') && x.endsWith('&')) {
          return '?'
        } else if (x.startsWith('&') && x.endsWith('&')) {
          return '&';
        } else {
          return '';
        }
      });

      // 适配微信公众号
      if (!title) {
        let result = await this.ctx.curl(url, {timeout: 3000});
        let body = result.data.toString();
        let pattern;

        pattern = /<title>(.+?)<\/title>/;
        if (pattern.test(body)) {
          finalTitle = pattern.exec(body)[1].trim();
          return {finalTitle, finalUrl};
        }

        pattern = /<meta property="og:title" content="(.+?)" \/>/;
        if (pattern.test(body)) {
          finalTitle = pattern.exec(body)[1].trim();
          return {finalTitle, finalUrl};
        }
      }
    } catch (e) {
      console.log(e);
    } finally {
      await browser.close();

      return this._fillFinalTile(finalTitle, finalUrl);
    }
  }

  _fillFinalTile(finalTitle, finalUrl) {
    if (finalUrl.includes('www.flipboard.cn')) {
      finalTitle = finalTitle + ' | Flipboard 红板报';
    }
    return {finalTitle, finalUrl};
  }


  async findRealWeixinUrlList(topics) {
    let url_list = [];
    let task = [];
    for (let topic of topics) {
      task.push(this.findRealWeixinUrl(topic.content));
    }
    url_list = await Promise.all(task);

    for (let i = 0; i < topics.length; i++) {
      let topic = topics[i];
      topic.content = url_list[i];
    }
  }

  async findRealWeixinUrl(url) {
    const headers = {
      'referer': `https://mp.data258.com/mp/search?type=article&key=tidb&sort=`,
      'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36'
    };
    const response = await this.ctx.curl(url, {headers: headers});
    let html = response.data.toString();
    let regResult = html.match(/(location.href=')([^']+)/g);
    if (regResult) {
      url = regResult[0].replace("location.href='", '');
    }
    return url;
  }


  async pollingBaidu(kw, now_page) {
    const cache_key = `baidu2:${kw}:${now_page}`;
    const result = await this.service.cacheSvc.get(cache_key);
    if (result) {
      return result;
    }

    return await new Promise((resolve, reject) => {
      let counter = 0;
      let timer = setInterval(async () => {
        let cache_value = await this.service.cacheSvc.get(cache_key);
        if (cache_value) {
          clearInterval(timer);
          return resolve(cache_value);
        }

        if (counter > 100) {
          clearInterval(timer);
          return reject('no baidu search data:' + cache_key);
        }
        counter++;
      }, 100);
    });
  }


  async pollingGoogle(kw, now_page) {
    const cache_key = `google2:${kw}:${now_page}`;
    const result = await this.service.cacheSvc.get(cache_key);
    if (result) {
      return result;
    }

    return await new Promise((resolve, reject) => {
      let counter = 0;
      let timer = setInterval(async () => {
        let cache_value = await this.service.cacheSvc.get(cache_key);
        if (cache_value) {
          clearInterval(timer);
          return resolve(cache_value);
        }

        if (counter > 100) {
          clearInterval(timer);
          return reject('no google search data:' + cache_key);
        }
        counter++;
      }, 100);
    });
  }


}

module.exports = SearchSvc;



